<?php

// ------------------------------------------------------------------------
// |@Author       : Jarmin <topextend@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2025-05-04 23:29:01
// |@----------------------------------------------------------------------
// |@LastEditTime : 2025-05-09 16:50:16
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <topextend@qq.com>
// |@----------------------------------------------------------------------
// |@Description  :
// |@----------------------------------------------------------------------
// |@FilePath     : Service.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2025 http://www.ladmin.cn All Rights Reserved.
// ------------------------------------------------------------------------
declare (strict_types=1);

namespace think\admin;

use think\App;
use think\admin\traits\ListenerTrait;
use think\admin\exception\BusinessException;

abstract class Service
{
    /**
     * 事件监听特性
     */
    use ListenerTrait;

    /**
     * App 实例
     * @var App
     */
    protected App $app;

    /**
     * 主仓储实例
     * @var Repository
     */
    protected Repository $repository;

    /**
     * 关联仓储实例集合
     * @var array<string, Repository>
     */
    protected array $repositories = [];

    /**
     * 关联服务实例集合
     * @var array<string, Service>
     */
    protected array $services = [];

    /**
     * 构造方法
     * @param App $app
     */
    public function __construct(App $app)
    {
        $this->app = $app;
        $this->initialize();
    }

    /**
     * 初始化方法
     * 子类必须实现此方法以初始化 repository 属性
     * @return void
     */
    abstract protected function initialize(): void;

    /**
     * 获取仓储实例
     * @param string|null $name 仓储名称，为空则返回主仓储
     * @return Repository
     * @throws BusinessException
     */
    protected function getRepository(?string $name = null): Repository
    {
        if (empty($name)) {
            return $this->repository;
        }

        if (!isset($this->repositories[$name])) {
            throw new BusinessException("仓储 {$name} 未注册");
        }

        return $this->repositories[$name];
    }

    /**
     * 获取服务实例
     * @param string $name 服务名称
     * @return Service
     * @throws BusinessException
     */
    protected function getService(string $name): Service
    {
        if (!isset($this->services[$name])) {
            throw new BusinessException("服务 {$name} 未注册");
        }

        return $this->services[$name];
    }

    /**
     * 注册关联仓储
     * @param string $name 仓储名称
     * @param Repository $repository 仓储实例
     * @return void
     */
    protected function registerRepository(string $name, Repository $repository): void
    {
        $this->repositories[$name] = $repository;
    }

    /**
     * 注册关联服务
     * @param string $name 服务名称
     * @param Service $service 服务实例
     * @return void
     */
    protected function registerService(string $name, Service $service): void
    {
        $this->services[$name] = $service;
    }

    /**
     * 检查仓储是否存在
     * @param string $name 仓储名称
     * @return bool
     */
    protected function hasRepository(string $name): bool
    {
        return isset($this->repositories[$name]);
    }

    /**
     * 注销关联仓储
     * @param string $name 仓储名称
     * @return void
     */
    protected function unregisterRepository(string $name): void
    {
        unset($this->repositories[$name]);
    }

    /**
     * 获取所有仓储实例
     * @return Repository[]
     */
    protected function getRepositories(): array
    {
        return array_merge([$this->repository], array_values($this->repositories));
    }

    /**
     * 检查服务是否存在
     * @param string $name 服务名称
     * @return bool
     */
    protected function hasService(string $name): bool
    {
        return isset($this->services[$name]);
    }

    /**
     * 注销关联服务
     * @param string $name 服务名称
     * @return void
     */
    protected function unregisterService(string $name): void
    {
        unset($this->services[$name]);
    }

    /**
     * 获取列表
     * @param array $params 查询参数
     * @param string|null $repository 仓储名称，为空则使用主仓储
     * @return array{items: array, total: int, page: int, limit: int}
     * @throws BusinessException
     */
    protected function list(array $params = [], ?string $repository = null): array
    {
        // 参数预处理
        $params = array_merge([
            'page' => 1,
            'limit' => 15,
            'order' => 'id desc',
            'where' => []
        ], $params);

        return $this->getRepository($repository)->list($params);
    }

    /**
     * 获取详情
     * @param int $id 记录ID
     * @param string|null $repository 仓储名称，为空则使用主仓储
     * @return array
     * @throws BusinessException
     */
    protected function detail(int $id, ?string $repository = null): array
    {
        if ($id <= 0) {
            throw new BusinessException('无效的记录ID');
        }

        $detail = $this->getRepository($repository)->detail($id);
        if (empty($detail)) {
            throw new BusinessException('记录不存在');
        }
    }

    /**
     * 创建记录
     * @param array $data 记录数据
     * @param string|null $repository 仓储名称，为空则使用主仓储
     * @return array 创建成功的记录
     * @throws BusinessException
     */
    protected function create(array $data, ?string $repository = null): array
    {
        if (empty($data)) {
            throw new BusinessException('创建数据不能为空');
        }

        // 触发创建前事件
        $this->event('before_create', $data);

        $repo = $this->getRepository($repository);

        // 创建记录
        $result = $repo->create($data);
        if (!$result) {
            throw new BusinessException('创建失败');
        }
        // 触发创建后事件
        $this->event('after_create', $result);
        // 返回创建后的完整记录
        return $repo->detail($result);
    }

    /**
     * 更新记录
     * @param int $id 记录ID
     * @param array $data 更新数据
     * @param string|null $repository 仓储名称，为空则使用主仓储
     * @return array 更新后的记录
     * @throws BusinessException
     */
    protected function update(int $id, array $data, ?string $repository = null): array
    {
        if ($id <= 0) {
            throw new BusinessException('无效的记录ID');
        }

        if (empty($data)) {
            throw new BusinessException('更新数据不能为空');
        }

        // 触发更新前事件
        $this->event('before_update', [$id, $data]);

        $repo = $this->getRepository($repository);

        // 检查记录是否存在
        $exists = $repo->detail($id);
        if (empty($exists)) {
            throw new BusinessException('记录不存在');
        }

        // 更新记录
        $result = $repo->update($id, $data);
        if (!$result) {
            throw new BusinessException('更新失败');
        }

        // 触发更新后事件
        $this->event('after_update', [$id, $data, $result]);

        // 返回更新后的完整记录
        return $repo->detail($id);
    }

    /**
     * 删除记录
     * @param int $id 记录ID
     * @param string|null $repository 仓储名称，为空则使用主仓储
     * @return bool
     * @throws BusinessException
     */
    protected function delete(int $id, ?string $repository = null): bool
    {
        if ($id <= 0) {
            throw new BusinessException('无效的记录ID');
        }

        $repo = $this->getRepository($repository);

        // 检查记录是否存在
        $exists = $repo->detail($id);
        if (empty($exists)) {
            throw new BusinessException('记录不存在');
        }

        // 触发删除前事件
        $this->event('before_delete', $id);

        // 删除记录
        $result = $repo->delete($id);
        if (!$result) {
            throw new BusinessException('删除失败');
        }

        // 触发删除后事件
        $this->event('after_delete', [$id, $result]);

        return true;
    }

    /**
     * 批量删除记录
     * @param array $ids 记录ID数组
     * @param string|null $repository 仓储名称，为空则使用主仓储
     * @return bool
     * @throws BusinessException
     */
    protected function batchDelete(array $ids, ?string $repository = null): bool
    {
        if (empty($ids)) {
            throw new BusinessException('删除ID不能为空');
        }

        // 过滤无效ID
        $ids = array_filter($ids, fn ($id) => $id > 0);
        if (empty($ids)) {
            throw new BusinessException('无有效的删除ID');
        }

        $repo = $this->getRepository($repository);

        // 检查记录是否都存在
        $exists = $repo->whereIn('id', $ids)->count();
        if ($exists !== count($ids)) {
            throw new BusinessException('部分记录不存在');
        }

        // 批量删除
        return $this->transaction(function () use ($repo, $ids) {
            return $repo->batchDelete($ids);
        });
    }

    /**
     * 开启事务
     * @throws BusinessException
     */
    protected function startTrans(): void
    {
        if (!isset($this->repository)) {
            throw new BusinessException('主仓储未初始化');
        }

        foreach ($this->getRepositories() as $repository) {
            $repository->startTrans();
        }
    }

    /**
     * 提交事务
     * @throws BusinessException
     */
    protected function commit(): void
    {
        if (!isset($this->repository)) {
            throw new BusinessException('主仓储未初始化');
        }

        foreach ($this->getRepositories() as $repository) {
            $repository->commit();
        }
    }

    /**
     * 回滚事务
     * @throws BusinessException
     */
    protected function rollback(): void
    {
        if (!isset($this->repository)) {
            throw new BusinessException('主仓储未初始化');
        }

        foreach ($this->getRepositories() as $repository) {
            $repository->rollback();
        }
    }

    /**
     * 执行事务操作
     * @param callable $callback 回调函数
     * @return mixed
     * @throws \Throwable
     */
    protected function transaction(callable $callback): mixed
    {
        try {
            $this->startTrans();
            $result = $callback();
            $this->commit();
            return $result;
        } catch (\Throwable $e) {
            $this->rollback();
            throw $e;
        }
    }

    /**
     * 关联查询
     * @param array $repositories 关联的仓储配置，格式：['repository_name' => ['relation_field' => 'field']]
     * @param array $data 主数据
     * @return array
     * @throws BusinessException
     */
    protected function withRelations(array $repositories, array $data): array
    {
        if (empty($data) || empty($repositories)) {
            return $data;
        }

        foreach ($repositories as $name => $config) {
            if (!$this->hasRepository($name)) {
                continue;
            }

            $repo = $this->getRepository($name);
            foreach ($config as $relation => $field) {
                // 处理单条数据
                if (isset($data[$field])) {
                    $data[$relation] = $repo->detail($data[$field]) ?: null;
                    continue;
                }

                // 处理数据集合
                if (isset($data['items']) && is_array($data['items'])) {
                    $ids = array_column($data['items'], $field);
                    if (!empty($ids)) {
                        $relations = $repo->whereIn('id', array_unique($ids))->select();
                        $relationMap = array_column($relations, null, 'id');
                        foreach ($data['items'] as &$item) {
                            $item[$relation] = $relationMap[$item[$field]] ?? null;
                        }
                    }
                }
            }
        }

        return $data;
    }
}
